package force;
import java.awt.geom.Point2D;


/**
 * This class represents a force applied in a specific direction and magnitude.
 */
public class ForceVector {
    // angle in degrees
    /**
     * @param myAngle my angle
     */
    private double myAngle;
    /**
     * @param ANGLE the angle of 180
     */
    static final int ANGLE = 180;
    // "speed" in pixels per second
    /**
     * @param myMagnitude the magnitude of the force
     */
    private double myMagnitude;
    /**
     * @param fullangle 360 degree
     */
     static final int FULLANGLE = 360;

    /**
     * Create a powerless force, i.e., with no magnitude.
     */
    public ForceVector() {
        this(0, 0);
    }

    /**
     * Create a force in the given direction with the given magnitude.
     * @param angle angle of the force
     * @param magnitude the magnitude of the force
     */
    public ForceVector(final double angle, final double magnitude) {
        setDirection(angle);
        setMagnitude(magnitude);
    }

    /**
     * Create a force whose direction and magnitude are determined by the
     * direction and distance between the two given points.
     * @param source source point
     * @param target target point
     */
    public ForceVector(final Point2D source, final Point2D target) {
        this(angleBetween(target, source),
                distanceBetween(target, source));
    }

    /**
     * Create a force that is identical to the given other force.
     * @param other force
     */
    public ForceVector(final ForceVector other) {
        this(other.getDirection(), other.getMagnitude());
    }

    /**
     * Reset this force to zero.
     */
    public final void reset() {
        setDirection(0);
        setMagnitude(0);
    }

    /**
     * @return Returns this force's magnitude (in pixels).
     */
    public double getMagnitude() {
        return myMagnitude;
    }

    /**
     * @return Returns this force's magnitude relative to the given
     * other force.More formally, returns the magnitude of this force
     * projected onto the given other force.
     * @param other force
     */
    public final double getRelativeMagnitude(final ForceVector other) {
        return -getMagnitude()
                * Math.cos(Math.toRadians(getAngleBetween(other)));
    }

    /**
     * Scales this force's magnitude by the given change value.
     * <UL>
     * <LI>A value of 1 leaves the magnitude unchanged
     * <LI>Values less than 1 reduce the magnitude
     * <LI>Values greater than 1 increase the magnitude
     * </UL>
     * @param change double
     */
    public final void scale(final double change) {
        setMagnitude(getMagnitude() * change);
    }

    /**
     * Sets this force's magnitude to the given value.
     * @param value magnitude
     */
    protected final void setMagnitude(final double value) {
        myMagnitude = value;
    }

    /**
     * @return Returns this force's direction (in degrees).
     */
    public final double getDirection() {
        // standardize between -360 and +360 (keep 360, -360, and 0 as distinct
        // values)
        final double offset = 0.001;
        double sign;
        if (myAngle < 0) {
            sign = 1;
        } else {
            sign = -1;
        }

        return ((myAngle + sign * offset) % FULLANGLE) - sign * offset;
    }

    /**
     * @return Returns the angle between this force and the given other force.
     * @param other force
     */
    public final double getAngleBetween(final ForceVector other) {
        return getDirection() - other.getDirection();
    }

    /**
     * Adjusts this force's direction by the given change value.
     * @param change change
     */
    public final void turn(final double change) {
        setDirection(getDirection() + change);
    }

    /**
     * Sets this force's direction to the given value.
     * @param value value
     */
    protected final void setDirection(final double value) {
        myAngle = value;
    }

    /**
     * @return Returns the change in the X direction represented by this force.
     */
    public final double getXChange() {
        return getMagnitude() * Math.cos(Math.toRadians(getDirection()));
    }

    /**
     * @return Returns the change in the Y direction represented by this force.
     */
    public final double getYChange() {
        return getMagnitude() * Math.sin(Math.toRadians(getDirection()));
    }

    /**
     * Returns a force that is the sum of this force and the given other force.
     * @param other force
     */
    public final void sum(final ForceVector other) {
        // double a1 = getAngle();
        // double a2 = other.getAngle();
        // double m1 = getMagnitude();
        // double m2 = other.getMagnitude();
        // double speed = Math.sqrt(m1 * m1 + m2 * m2 + 2 * m1 * m2 *
        // Math.cos(Math.toRadians(a1 - a2)));
        // double angle = Math.toDegrees(Math.asin(m2 *
        // Math.sin(Math.toRadians(a2 - a1)) / speed)) + a1;
        // return new Force(angle, speed);

        // more readable, although slightly slower
        double dx = getXChange() + other.getXChange();
        double dy = getYChange() + other.getYChange();
        setDirection(angleBetween(dx, dy));
        setMagnitude(distanceBetween(dx, dy));
    }
    /**
     * @param other sum gravity
     */
    public final void sum(final Gravity other) {
        double dx = getXChange() + other.getXChange();
        double dy = getYChange() + other.getYChange();
        setDirection(angleBetween(dx, dy));
        setMagnitude(distanceBetween(dx, dy));
    }
    /**
     * @param other sum viscosity
     */
    public final void sum(final Viscosity other) {
        double dx = getXChange() + other.getXChange();
        double dy = getYChange() + other.getYChange();
        setDirection(angleBetween(dx, dy));
        setMagnitude(distanceBetween(dx, dy));
    }

    /**
     * Returns a force that is the difference between this force and the given
     * other force.
     * @param other force
     */
    public final void difference(final ForceVector other) {
        other.negate();
        sum(other);
    }

    /**
     * Returns a force of the same magnitude, but in the opposite direction as
     * this force.
     */

    public final void negate() {
        turn(ANGLE);
    }

    /**
     * @return Returns the average of this force with the given other force.
     * @param other force
     */
    public final ForceVector average(final ForceVector other) {
        return new ForceVector((getDirection() + other.getDirection()) / 2.0,
                (getMagnitude() + other.getMagnitude()) / 2.0);
    }

    /**
     * Return true if this force has the same magnitude and direction
     * as the given other force.
     * @param force object
     */
    @Override
    public final boolean equals(final Object force) {
        final double myEPSILON = 0.000001;
        try {
            ForceVector other = (ForceVector) force;
            return (Math.abs(getMagnitude() - other.getMagnitude())
                    < myEPSILON && Math
                    .abs(getDirection() - other.getDirection()) < myEPSILON);
        } catch (ClassCastException e) {
            return false;
        }
    }

    /**
     * @return Returns this force's values formatted as a string.
     */
    @Override
    public final String toString() {
        return String.format("(%1.2f, %1.2f)", getDirection(), getMagnitude());
    }

    /**
     * @return Returns the distance between given two points
     * @param p1 point2D
     * @param p2 point2D
     */
    public static double distanceBetween(final Point2D p1, final Point2D p2) {
        return distanceBetween(p1.getX() - p2.getX(), p1.getY() - p2.getY());
    }

    /**
     * @return Returns the distance represented by the given dx and dy
     * @param dx distance difference in x
     * @param dy distance difference in y
     */
    public static double distanceBetween(final double dx, final double dy) {
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * @return Returns the angle between the given two points
     * @param p1 point2D
     * @param p2 point2D
     */
    public static double angleBetween(final Point2D p1, final Point2D p2) {
        return angleBetween(p1.getX() - p2.getX(), p1.getY() - p2.getY());
    }

    /**
     * @return Returns the angle represented by the given dx and dy
     * @param dx distance difference in x
     * @param dy distance difference in y
     */
    public static double angleBetween(final double dx, final double dy) {
        return Math.toDegrees(Math.atan2(dy, dx));
    }
    /**
     * @return myAngle
     */
    public final double getmyAngle() {
        return myAngle;
    }
    /**
     * @return myMagnitude
     */
    public final double getmyMagnitude() {
        return myMagnitude;
    }
}
